UnLua & C++ 交互

Lua 中调用一般的 UFUNCTION 反射比较简单直接,这里以从 Lua 中调用 CFUNCTION 为例简单介绍两端交互。

具体为在 Lua 中的调用:local Table = UE4.UTestUtils.LuaCFunction(Val0, Val1),并返回一个 Lua Table : { "Val0" = Val0, "Val1" = Va1 }

定义

首先通过 ADD_STATIC_CFUNCTION 静态导出这个原生的 FGlueFunction

具体的实现可以在 UnLuaEx.h 中找到。

1
2
3
4
5
6
7
8
9
10
11
// Test.h
struct lua_State;

UCLASS()
class UTestUtils : public UBlueprintFunctionLibrary
{
GENERATED_BODY()

public:
static int LuaCFunction(lua_State* L);
}
1
2
3
4
5
// Test.cpp
BEGIN_EXPORT_REFLECTED_CLASS(UTestUtils)
ADD_STATIC_CFUNTION(LuaCFunction)
END_EXPORT_CLASS(UTestUtils)
IMPLEMENT_EXPORTED_CLASS(UTestUtils)

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int UTestUtils::LuaCFunction(lua_State* L)
{
const int Top = lua_gettop(L);
if (Top == 2)
{
int32 Val0 = lua_tointeger(L, 1);
int32 Val1 = lua_tointeger(L, 2);
UUnLuaFunctionLibrary::CreateLuaTable(L, "Val0", Val0, "Val1", Val1);
return 1;
}

luaL_error(L, "Call UTestUtils::LuaCFunction error! argc = %d", Top);
return 0;
}

CFUNCTIONLua 中调用时,会传入当前的 lua_State* L ,可以通过这个LLuaStack 进行访问与写入。

首先这里的 Top = lua_gettop(L) 会根据 cast_int(L->top - (L->ci->func + 1)) 计算出返回参数的个数,比如这里的 (Val0, Val1) 就是 2 个参数。

接着通过 lua_tointeger(L, i) 将第 i 个参数取出。

然后通过 UUnLuaFunctionLibrary::CreateLuaTable,创建一个 Lua Table,进行赋值与写入,这个方法是实现的重点。

最后如果合法,return 1,返回写入 LuaStack 中返回值的个数,也就是有 1LuaTable 被写入了栈。

具体的与 lua 层交互的原始方法实现可以在 lapi.c 中找到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
template<typename... Types>
UnLua::FLuaTable UUnLuaFunctionLibrary::CreateLuaTable(lua_State* L, Types&&... Args)
{
const auto LuaEnv = UnLua::FLuaEnv::FindEnv(L);
lua_newtable(L);

PushKeyValue(L, Forward<Types>(Args)...);

return UnLua::FLuaTable(LuaEnv, -1);
}

template<typename K, typename V, typename... Types>
void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, K&& Key, V&& Value, Types&&... Args)
{
PushKeyValue(L, Forward<K>(Key), Forward<V>(Value));
PushKeyValue(L, Forward<Types>(Args)...);
}

template<typename K, typename V>
void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, K&& Key, V&& Value)
{
UnLua::Push(L, Forward<K>(Key));
UnLua::Push(L, Forward<V>(Value));
lua_rawset(L, -3);
}

这个 UUnLuaFunctionLibrary::CreateLuaTableL 对应的 LuaStack 中写入了一个赋值好的 LuaTable

首先通过 lua_newtable(L) 创建了一个空的 LuaTable,然后进行 PushKeyValue 的递归调用。

针对一次 PushKeyValue,首先会往 LuaStack 中压入 KeyValue,压入后先前的 LuaTable 处于在 StatckIndex=-3 的位置。

image-20240527120544564

然后针对 StackIndex = -3位置(也就是当前栈里的 LuaTable)调用 lua_rawset 方法,将 KeyValue 弹出并打包成参数塞进 LuaTable

这里的 lua_rawset 实际上类似 lua_settable。对于 lua_settable,会找到元方法 __newindex 并调用,对于 lua_rawset,则会调用默认的 __newindex 方法:

1
2
3
__newindex = function(table, key, value)
rawset(table, key, value)
end

这样,就完成了一个这样的方法实现。

给出 UUnluaFunctionLibrary 的更多扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// LuaTable

template<typename... Types>
UnLua::FLuaTable UUnLuaFunctionLibrary::CreateLuaTable(lua_State* L, Types&&... Args)
{
const auto LuaEnv = UnLua::FLuaEnv::FindEnv(L);
lua_newtable(L);

PushKeyValue(L, Forward<Types>(Args)...);

return UnLua::FLuaTable(LuaEnv, -1);
}

template<typename... Types>
UnLua::FLuaTable UUnLuaFunctionLibrary::CreateLuaArray(lua_State* L, const Types&... Args)
{
const auto LuaEnv = UnLua::FLuaEnv::FindEnv(L);
lua_newtable(L);

PushKeyValue<1>(L, Forward<Types>(Args)...);

return UnLua::FLuaTable(LuaEnv, -1);
}

// ------------------------

template<typename K, typename V>
void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, K&& Key, V&& Value)
{
UnLua::Push(L, Forward<K>(Key));
UnLua::Push(L, Forward<V>(Value));
lua_rawset(L, -3);
}

template<typename K, typename V, typename... Types>
void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, K&& Key, V&& Value, Types&&... Args)
{
PushKeyValue(L, Forward<K>(Key), Forward<V>(Value));
PushKeyValue(L, Forward<Types>(Args)...);
}

template<int N, typename T>
void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, T&& Arg)
{
UnLua::Push(L, N);
UnLua::Push(L, Forward<T>(Arg));
lua_rawset(L, -3);
}

template<int N, typename T, typename... Types>
void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, T&& Arg0, Types&&... Args)
{
PushKeyValue<N>(L, Forward<T>(Arg0));
PushKeyValue<N + 1>(L, Forward<Types>(Args)...);
}

template<typename K, typename T>
void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, K&& Key, TArray<T>& Array)
{
UnLua::Push(L, Forward<K>(Key));

lua_newtable(L);
for (int Index = 0; Index < Array.Num(); Index++)
{
UnLua::Push(L, Index + 1);
UnLua::Push(L, Array[Index]);
lua_rawset(L, -3);
}

lua_rawset(L, -3);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Struct

template <typename T>
typename TEnableIf<TIsUStruct<T>::Value, T*>::Type
UUnLuaFunctionLibrary::GetValue(lua_State* L, int32 Index)
{
const auto& Env = UnLua::FLuaEnv::FindEnvChecked(L);

const auto StructType = Env.GetPropertyRegistry()->CreateTypeInterface(L, Index);
if (!StructType)
return nullptr;

const auto StructProperty = CastField<FStructProperty>(StructType->GetUProperty());
if (StructProperty == nullptr)
return nullptr;

if (!StructProperty->Struct->IsChildOf(TBaseStructure<T>::Get()))
return nullptr;

return static_cast<T*>(GetCppInstanceFast(L, Index));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// Json

template<typename K>
void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, K&& Key, const TSharedPtr<FJsonValue>& Value)
{
UnLua::Push(L, Forward<K>(Key));
PushValue(L, Value);
lua_rawset(L, -3);
}

void UUnLuaFunctionLibrary::PushValue(lua_State* L, const TSharedPtr<FJsonObject>& JsonObject)
{
lua_newtable(L);
for (const auto& KeyValue : JsonObject->Values)
{
PushKeyValue(L, KeyValue.Key, KeyValue.Value);
}
}

void UUnLuaFunctionLibrary::PushValue(lua_State* L, const TArray<TSharedPtr<FJsonValue>>& JsonValues)
{
lua_newtable(L);
for (auto i = 0; i < JsonValues.Num(); i++)
{
PushKeyValue(L, i + 1, JsonValues[i]);
}
}

void UUnLuaFunctionLibrary::PushValue(lua_State* L, const TSharedPtr<FJsonValue>& Value)
{
switch (Value->Type)
{
case EJson::String: UnLua::Push(L, Value->AsString()); break;
case EJson::Number: UnLua::Push(L, Value->AsNumber()); break;
case EJson::Boolean: UnLua::Push(L, Value->AsBool()); break;
case EJson::Array: PushValue(L, Value->AsArray()); break;
case EJson::Object: PushValue(L, Value->AsObject()); break;
default: lua_pushnil(L); break;
}
}

// ------------------------

int UUnLuaFunctionLibrary::GetLuaTableFromJsonPath(lua_State* L)
{
const int Top = lua_gettop(L);
if (Top != 1)
{
LogWarning(TEXT("Args count error. argc = %d"), Top);
return 0;
}

auto JsonFilePath = FString(UTF8_TO_TCHAR(lua_tostring(L, 1)));
if (!FPaths::FileExists(JsonFilePath))
{
LogWarning(TEXT("Json file path '%s' not found."), *JsonFilePath);
return 0;
}

FString FileContent;
if (FFileHelper::LoadFileToString(FileContent, *JsonFilePath))
{
TSharedPtr<FJsonObject> JsonObject;
auto JsonReader = TJsonReaderFactory<>::Create(FileContent);
if (FJsonSerializer::Deserialize(JsonReader, JsonObject))
{
PushValue(L, JsonObject);
}
else
{
LogWarning(TEXT("Parse json from file '%s' failed."), *JsonFilePath);
return 0;
}
}
else
{
LogWarning(TEXT("Load file path '%s' failed."), *JsonFilePath);
return 0;
}

return 1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Call

template <typename... T>
UnLua::FLuaRetValues UUnLuaFunctionLibrary::InnerCall(UObjectBaseUtility* Obj, lua_State *L, const char *FuncName, T&&... Args)
{
const auto LuaEnv = UnLua::FLuaEnv::FindEnv(L);

if (UnLua::GetObjectLuaInstance(Obj))
{
const UnLua::FLuaTable LuaInstance(UnLua::FLuaValue(LuaEnv, -1));
const UnLua::FLuaFunction LuaFunc(LuaEnv, LuaInstance, FuncName);

if (LuaFunc.IsValid())
{
auto Ret = LuaFunc.Call(LuaInstance, Forward<T>(Args)...);
lua_pop(L, 1);
return Ret;
}
}

return UnLua::FLuaRetValues(LuaEnv, 0);
}

// Class:
template <typename... T>
UnLua::FLuaRetValues InnerCall(const char *FuncName, T&&... Args)
{
return UUnLuaFunctionLibrary::InnerCall(this, UnLua::GetState(), FuncName, Args);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// Delegate

template<typename... T>
struct FUnLuaTableCallback
{
FUnLuaTableCallback(TSharedPtr<UnLua::FLuaTableRef> Table, TSharedPtr<UnLua::FLuaFunction> Func) : Table(Table), Func(Func) {}

inline void operator() (T... Args)
{
Func->Call(*Table.Get(), Forward<T>(Args)...);
}

private:
TSharedPtr<UnLua::FLuaTableRef> Table;
TSharedPtr<UnLua::FLuaFunction> Func;
};


#define DECLARE_MULTICAST_DELEGATE_LUA_HELPER(OwnerType, DelegateName, ...) \
private: \
TMap<FString, FDelegateHandle> DelegateName##LuaHandles; \
public: \
static int Bind##DelegateName(lua_State* L) \
{ \
if (!UUnLuaFunctionLibrary::CheckParamsCount(L, 3)) { return luaL_error(L, "Invalid parameters count."); } \
auto Self = static_cast<OwnerType*>(GetCppInstanceFast(L, 1)); \
auto TableValue = UnLua::FLuaValue(L, 2); \
if (TableValue.GetType() != LUA_TTABLE) { return luaL_error(L, "Parameter 2 is invalid; it should be a table."); } \
auto Key = UUnLuaFunctionLibrary::ToString(L, 2); \
if (Self->DelegateName##LuaHandles.Contains(Key)) { return 0; } \
auto FuncValue = UnLua::FLuaValue(L, 3); \
if (FuncValue.GetType() != LUA_TFUNCTION) { return luaL_error(L, "Parameter 3 is invalid; it should be a function."); } \
TSharedPtr<UnLua::FLuaTableRef> Table = MakeShareable(new UnLua::FLuaTableRef(L, TableValue)); \
TSharedPtr<UnLua::FLuaFunction> Func = MakeShareable(new UnLua::FLuaFunction(L, FuncValue)); \
Self->DelegateName##LuaHandles.Emplace(Key, Self->DelegateName.AddLambda(FUnLuaTableCallback<__VA_ARGS__>(Table, Func))); \
return 0; \
} \
static int Unbind##DelegateName(lua_State* L) \
{ \
if (!UUnLuaFunctionLibrary::CheckParamsCount(L, 2)) { return luaL_error(L, "Invalid parameters count."); } \
auto Self = static_cast<OwnerType*>(GetCppInstanceFast(L, 1)); \
auto TableValue = UnLua::FLuaValue(L, 2); \
if (TableValue.GetType() != LUA_TTABLE) { return luaL_error(L, "Parameter 2 is invalid; it should be a table."); } \
auto Key = UUnLuaFunctionLibrary::ToString(L, 2); \
if (!Self->DelegateName##LuaHandles.Contains(Key)) { return 0; } \
Self->DelegateName.Remove(Self->DelegateName##LuaHandles[Key]); \
Self->DelegateName##LuaHandles.Remove(Key); \
return 0; \
}


#define ADD_MULTICAST_DELEGATE_LUA_HELPER_FUNC(DelegateName) \
ADD_STATIC_CFUNTION(Bind##DelegateName) \
ADD_STATIC_CFUNTION(Unbind##DelegateName)
#pragma endregion Delegate Lua Helper

参考

Lua 5.4 Reference4.6 – Functions and Types

Lua 虚拟栈交互流程

Unlua 解析